/*

  xmunipack - archive


  Copyright © 2009 - 2011 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/


#include "xmunipack.h"
#include <wx/wx.h>
#include <wx/filename.h>
#include <wx/dir.h>
#include <wx/utils.h>
#include <wx/filesys.h>
#include <vector>

using namespace std;


// -- xTraverser - helper for dir rename

class xTraverser: public wxDirTraverser
{
public:
  xTraverser(const wxString& o,const wxString& n): older(o), newer(n) {}

  virtual wxDirTraverseResult OnFile(const wxString& filename)
  {
    wxFileName oname(filename);
    wxFileName fname(newer,oname.GetFullName());

    if( oname.GetExt() == "fm" ) {

      wxLogDebug("wxDirTravrseResult OnFile"+filename+"->"+fname.GetFullPath());

      MuniThumbnail fth(filename);
      fth.Save(fname.GetFullPath());

      wxFileName f(filename);
      f.SetEmptyExt();

      wxString fname;
      wxDir dir(f.GetPath());
      bool c = dir.GetFirst(&fname,f.GetName()+".*",wxDIR_FILES);
      while(c) {
	wxFileName w(f.GetPath(),fname);
	wxRemoveFile(w.GetFullPath());
	c = dir.GetNext(&fname);
      }
    }

    return wxDIR_CONTINUE;
  }

  virtual wxDirTraverseResult OnDir(const wxString& dirname)
  {
    wxFileName oname(dirname);
    wxFileName fname(newer,oname.GetFullName());
    wxLogDebug("wxDirTraverseResult OnDir"+dirname+" -> "+fname.GetFullPath());

    oname.Rmdir();
    fname.Mkdir();

    return wxDIR_CONTINUE;
  }


private:

  const wxString older,newer;
};



// xArchiveThread --- archive load thread

class xArchiveThread: public wxThread
{
private:

  wxEvtHandler *handler;
  wxString path;

  ExitCode Entry();

public:
  xArchiveThread(wxEvtHandler *h, const wxString& p):
    wxThread(wxTHREAD_DETACHED),handler(h),path(p)
  {
    wxASSERT(handler && ! path.IsEmpty());
  }

  virtual ~xArchiveThread() {
    wxCriticalSectionLocker enter(static_cast<MuniArchive *>(handler)->loaderCS);
    static_cast<MuniArchive *>(handler)->loader = 0;
  }

};


wxThread::ExitCode xArchiveThread::Entry()
{
  wxFileName dirname(path);
  wxDir dir(dirname.GetPath());
  wxASSERT(dir.IsOpened());

  wxString filename;
  bool cont = dir.GetFirst(&filename, "*.fm", wxDIR_FILES);
  while ( cont ) {

    if( TestDestroy() ) return (wxThread::ExitCode) 1;

    wxFileName f(dirname.GetPath(),filename);
    MuniThumbnail fm(f.GetFullPath());
    if( fm.IsOk() ) {

      MetaOpenEvent ev(EVT_META_OPEN,ID_ARCHIVE);
      ev.meta = fm.GetMeta();
      wxQueueEvent(handler,ev.Clone());
    }

    cont = dir.GetNext(&filename);
  }

  wxQueueEvent(handler,new MetaOpenEvent(EVT_META_OPEN,ID_ARCHIVE_FINISH));
  
  return (wxThread::ExitCode) 0;
}




// --- MuniArchive

MuniArchive::MuniArchive(wxEvtHandler *h, const wxString& p): 
  wxEvtHandler(), loader(0), root(p), cpath(wxFileName::GetPathSeparator()), 
  sep(wxFileName::GetPathSeparator()),handler(h)
{
  wxASSERT(root.EndsWith(sep));

#ifdef __WXDEBUG__
  wxFileName r(root);
  wxASSERT(r.DirExists());
#endif

  wxFileName a(root);
  a.AppendDir("Archive");
  if( ! a.DirExists() ) a.Mkdir(0777,wxPATH_MKDIR_FULL);
  wxASSERT(a.DirExists());

  wxFileName w(root);
  w.AppendDir("Workplace");
  if( ! w.DirExists() ) w.Mkdir(0777,wxPATH_MKDIR_FULL);
  wxASSERT(w.DirExists());

  Bind(EVT_META_OPEN,&MuniArchive::OnMetaOpen,this);
  Bind(EVT_META_OPEN,&MuniArchive::OnMetaFinish,this,ID_ARCHIVE_FINISH);
}

wxString MuniArchive::GetRoot() const
{
  return root;
}

bool MuniArchive::IsReadOnly() const
{
  return cpath.EndsWith("Workplace"+sep);
}


vector<FitsMeta> MuniArchive::GetMeta() const
{
  wxFileName dirname(GetFullPath());
  wxDir dir(dirname.GetPath());
  wxASSERT(dir.IsOpened());

  vector<FitsMeta> mlist;

  wxString filename;
  bool cont = dir.GetFirst(&filename, "*.fm", wxDIR_FILES);
  while ( cont ) {

    wxFileName f(dirname.GetPath(),filename);
    MuniThumbnail fm(f.GetFullPath());
    if( fm.IsOk() )
      mlist.push_back(fm.GetMeta());

    cont = dir.GetNext(&filename);
  }

  return mlist;
}

void MuniArchive::AppendMeta(const FitsMeta& fmeta)
{
  long index = NextAvailableIndex();
  AppendMeta(fmeta,index);
}

void MuniArchive::AppendMeta(const std::vector<FitsMeta>& flist)
{
  long index = NextAvailableIndex();

  for(size_t k = 0; k < flist.size(); k++) {
    AppendMeta(flist[k],index);
    index++;
  }
}

void MuniArchive::AppendMeta(const FitsMeta& fmeta, long index)
{
  wxFileName w(GetFullPath());

  MuniThumbnail fm(fmeta);
  wxString thumb;
  thumb.Printf("thumb_%d.fm",(int) index);
  wxASSERT(! thumb.IsEmpty());
  w.SetFullName(thumb);
  fm.Save(w.GetFullPath());
}


long MuniArchive::NextAvailableIndex() const
{
  wxFileName w(GetFullPath());

  wxDir dir(w.GetPath());
  wxASSERT(dir.IsOpened());

  wxString filename;
  bool cont = dir.GetFirst(&filename, "*.fm", wxDIR_FILES);
  long index = -1;
  while(cont) {
    wxString a = filename.AfterFirst('_');
    wxString b = a.BeforeFirst('.');
    long l;
    if( ! b.IsEmpty() && b.ToLong(&l) && l > index )
      index = l;
    cont = dir.GetNext(&filename);
  }
  return index < 0 ? 0 : index + 1;
}


void MuniArchive::ChangeDir(const wxString& next)
{
  wxASSERT(! next.IsEmpty());
  //  wxLogDebug(_("MuniArchive::ChangeDir ")+next);

  if( next == ".." ) {
    if( cpath != sep ) {
      wxASSERT(cpath.EndsWith(sep));
      wxFileName c(cpath);
      c.RemoveLastDir();
      cpath = c.GetPathWithSep();
    }
  }
  else {

    if( IsAbsPath(next) ){

      cpath = next;

    }
    else {

      wxASSERT(cpath.EndsWith(sep) && cpath.StartsWith(sep));
      wxFileName c(cpath);

      wxFileName n(next.EndsWith(sep) ? next : next + sep);
      wxArrayString p = n.GetDirs();
      for(size_t l = 0; l < p.GetCount(); l++)
	c.AppendDir(p[l]);
	
      cpath = c.GetPathWithSep();
    }
  }
}

void MuniArchive::MakeDir(const wxString& next)
{
  wxASSERT(! next.IsEmpty());
  wxLogDebug("MuniArchive::MkDir "+next);

  wxFileName dirname(GetFullPath());

  dirname.AppendDir(next);
  dirname.Mkdir();
}

void MuniArchive::DeleteDir(const wxString& next)
{
  wxASSERT(! next.IsEmpty());
  wxLogDebug("MuniArchive::RmDir "+next);

  wxFileName dirname(GetFullPath(next));
  wxDir d(dirname.GetPath());
  wxString f;
  bool c = d.GetFirst(&f,wxEmptyString,wxDIR_FILES);
  while(c) {
    wxFileName w(dirname.GetPath(),f);
    wxRemoveFile(w.GetFullPath());
    c = d.GetNext(&f);
  }
  dirname.Rmdir();
}

void MuniArchive::RenameDir(const wxString& path, const wxString& nlabel)
{
  wxASSERT(! nlabel.IsEmpty());
  wxLogDebug("MuniArchive::RenameDir "+path+" -> "+nlabel);

  wxString fullpath = GetFullPath(path);

  wxFileName dirname(fullpath);
  dirname.RemoveLastDir();
  dirname.AppendDir(nlabel);
  dirname.Mkdir();

  wxFileName dname(fullpath);
  wxDir dir(dname.GetFullPath());
  xTraverser traverser(dname.GetFullPath(),dirname.GetFullPath());
  dir.Traverse(traverser);
  
  DeleteDir(DissolveRoot(dname.GetFullPath()));
  cpath = DissolveRoot(dirname.GetFullPath());
}

wxArrayString MuniArchive::GetDirs(const wxString& dir) const
{
  return GetItems(wxEmptyString, wxDIR_DIRS, dir);
}

wxArrayString MuniArchive::GetFiles(const wxString& dir) const
{
  wxArrayString p = GetItems("*.fm", wxDIR_FILES, dir);
  wxArrayString q;

  wxFileName dname(GetFullPath());

  for(size_t i = 0; i < p.GetCount(); i++) {
    wxFileName n(dname.GetPath(),p[i]);
    q.Add(n.GetFullPath());
  }

  return q;
}

wxArrayString MuniArchive::GetItems(const wxString& filespec, int flags, 
				    const wxString& directory) const
{
  wxBusyCursor wait;

  wxFileName dname(GetFullPath(directory));
  wxDir dir(dname.GetPath());
  wxASSERT(dir.IsOpened());

  wxArrayString items;
  wxString filename;
  bool cont = dir.GetFirst(&filename, filespec, flags);
  while(cont) {
    wxLogDebug(dname.GetPathWithSep() + ": "+ filename);
    items.Add(filename);
    cont = dir.GetNext(&filename);
  }
  return items;
}


wxString MuniArchive::GetFullPath(const wxString& path) const
{
  wxFileName dirname(root);
  wxFileName c(cpath);

  if( ! path.IsEmpty() ) {
    wxString xpath = path;
    if( ! xpath.EndsWith(sep) ) xpath += sep;
    wxASSERT(xpath.EndsWith(sep));

    wxFileName d = wxFileName(xpath);
    wxASSERT(d.IsOk());

    if( xpath.StartsWith(sep) )
      c = d;
    else {
      wxArrayString p = d.GetDirs();
      for(size_t l = 0; l < p.GetCount(); l++)
	c.AppendDir(p[l]);
    }
  }

  if( c.IsOk() ) {
    wxArrayString p = c.GetDirs();
    for(size_t l = 0; l < p.GetCount(); l++)
      dirname.AppendDir(p[l]);
  }


  wxLogDebug("MuniArchive::GetFullPath "+root+" / "+cpath+" = "+
  	     dirname.GetPathWithSep()+" - "+path);

  return dirname.GetPathWithSep();
}

wxString MuniArchive::DissolveRoot(const wxString& path) const
{
  wxString fullpath = GetFullPath();
  fullpath = root;

  if( path.StartsWith(fullpath) ) {
    wxLogDebug("Disolved: "+fullpath+" "+path+" "+path.Mid(fullpath.Len()-1));
    return path.Mid(fullpath.Len()-1);
  }
  return path;
}

wxString MuniArchive::GetAbsPath() const
{
  return cpath;
}

bool MuniArchive::IsAbsPath(const wxString& path) const
{
  return path.StartsWith(sep);
}

void MuniArchive::DeleteMeta(const FitsMeta& fmeta)
{
  wxString path = GetFullPath();
  wxDir dir(path);
  wxASSERT(dir.IsOpened());

  wxString filename;
  bool cont = dir.GetFirst(&filename,"*.fm",wxDIR_FILES);
  while(cont) {

    wxFileName name(path,filename);
    
    MuniThumbnail fm(name.GetFullPath());
    if( fm.GetURL() == fmeta.GetURL() ) {

      wxFileName f(path,filename);
      f.SetEmptyExt();
      wxString b = f.GetName() + ".*";
      wxLogDebug(b);
      wxString fname;
      wxDir d(path);
      bool c = d.GetFirst(&fname,f.GetName()+".*",wxDIR_FILES);
      while(c) {
	wxFileName w(f.GetPath(),fname);
	wxLogDebug(w.GetFullPath());
	wxRemoveFile(w.GetFullPath());
	c = d.GetNext(&fname);
      }
    }
    cont = dir.GetNext(&filename);
  }
}

void MuniArchive::DeleteMeta(const vector<FitsMeta>& ls)
{
  for(vector<FitsMeta>::const_iterator f = ls.begin(); f != ls.end(); ++f)
    DeleteMeta(*f);
}

void MuniArchive::FlushMeta(const vector<FitsMeta>& add,
			    const vector<FitsMeta>& del)
{
  AppendMeta(add);
  DeleteMeta(del);
}



void MuniArchive::SetEventHandler(wxEvtHandler *h)
{
  handler = h;
}


void MuniArchive::OnMetaOpen(MetaOpenEvent& event)
{
  //  wxLogDebug(_("MuniArchive::OnMetaRender %d: ")+event.meta.GetName(),event.GetId()==ID_ARCHIVE_FINISH);
  if( handler )
    wxQueueEvent(handler,event.Clone());
}

void MuniArchive::StopLoadMeta()
{
  wxLogDebug("Deleting meta load ...");

  {
    wxCriticalSectionLocker enter(loaderCS);
    if( loader )
      loader->Delete();
  }

  while(true) {
    {
      wxCriticalSectionLocker enter(loaderCS);
      if( ! loader ) break;
    }
    wxThread::This()->Sleep(1);
  }
}

void MuniArchive::LoadMeta()
{
  wxLogDebug("Starting meta load...");

  if( ! handler ) return;

  StopLoadMeta();

  loader = new xArchiveThread(this,GetFullPath());
  wxThreadError code = loader->Create();
  wxASSERT(code == wxTHREAD_NO_ERROR);
  loader->Run();
}

void MuniArchive::OnMetaFinish(MetaOpenEvent& event)
{
  wxLogDebug("MuniArchive::OnMetaFinish");
}
